home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994…tember: Reference Library / Dev.CD Sep 94.toast / Periodicals / develop / develop Issue 6 / develop 6 code / TCP / NewsWatcher / NewsWatcher 2.0d15 source / source / newsrc.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-08-27  |  27.7 KB  |  1,054 lines  |  [TEXT/KAHL]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     newsrc.c
  4.  
  5.     This module handles newsrc-format files, including opening
  6.     and saving user group lists from and to disk in newsrc format,
  7.     and getting and sending newsrc files from and to remote hosts. 
  8.     
  9.     Portions copyright © 1990, Apple Computer.
  10.     Portions copyright © 1993, Northwestern University.
  11.  
  12. ----------------------------------------------------------------------------*/
  13.  
  14. #include <stdio.h>
  15. #include <string.h>
  16. #include <stdlib.h>
  17. #include <errno.h>
  18.  
  19. #include "dlgutil.h"
  20. #include "glob.h"
  21. #include "newsrc.h"
  22. #include "util.h"
  23. #include "prefs.h"
  24. #include "ftp.h"
  25. #include "open.h"
  26. #include "full.h"
  27. #include "resize.h"
  28. #include "mark.h"
  29. #include "nntp.h"
  30. #include "wind.h"
  31. #include "sfutil.h"
  32. #include "log.h"
  33.  
  34.  
  35.  
  36. #define kHostDlg            129            /* Remote host dialog */
  37. #define kRemoteHost            4
  38. #define kRemoteLogin        6
  39. #define kRemotePassword        8
  40. #define kRemotePath            10
  41. #define kAutoGetPut            11
  42. #define kSavePassword        12
  43.  
  44.  
  45.  
  46. #define kCheckSaveID        133            /* Save confirm dialog */
  47.  
  48.  
  49.  
  50. #define kDeletedGroupsDlg                142            /* Deleted groups dialog */
  51. #define kDeletedGroupsTheInfoItem        3
  52.  
  53.  
  54.  
  55.  
  56. /* The following globals are used when parsing newsrc lists. */
  57.  
  58. static TGroup     **gUserGroupArray;            /* handle to user group array under construction */
  59. static short     gNumUserGroups;                /* number of user groups */
  60. static short     gNumUserGroupsAllocated;    /* number of user groups allocated */
  61. static Handle     gUnsubscribed;                /* handle to list of unsubscribed newsrc lines */
  62. static long     gUnsubscribedLen;            /* length of list of unsubscribed newsrc lines */
  63. static long     gUnsubscribedAllocated;        /* number of bytes allocated for gUnsubscribed */
  64. static Handle    gDeleted;                    /* handle to list of deleted groups */
  65. static long        gDeletedLen;                /* length of list of deleted groups */
  66. static long        gDeletedAllocated;            /* number of bytes allocated for gDeleted */
  67. static char     *gPos;                        /* current parsing position in newsrc list */
  68. static TGroup     gTheGroup;                    /* current group under construction */
  69.  
  70. /* The following global variables are used when constructing newsrc lists. */
  71.  
  72. static Handle    gNewsrc;                    /* newsrc list under construction */
  73. static long        gNewsrcLength;                /* length of newsrc */
  74. static long        gNewsrcAllocated;            /* number of bytes allocated in newsrc */
  75.  
  76.  
  77.  
  78. /*----------------------------------------------------------------------------
  79.     DoHostDialog 
  80.     
  81.     Presents the remote host dialog.
  82.     
  83.     Exit:    function result = true if OK clicked, false if Cancel clicked.
  84. ----------------------------------------------------------------------------*/
  85.  
  86. static Boolean DoHostDialog (void)
  87. {
  88.     DialogPtr dlg;
  89.     short item;
  90.     CStr255 tempStr;
  91.     short len;
  92.     CStr255 host;
  93.     CStr255 name;
  94.     char path[32];
  95.     char password[32];
  96.     
  97.     dlg = MyGetNewDialog(kHostDlg);
  98.     strcpy(host, gPrefs.host);
  99.     DlgSetCString(dlg, kRemoteHost, host);
  100.     SetItemMaxLength(dlg, kRemoteHost, 255);
  101.     strcpy(name, gPrefs.name);
  102.     DlgSetCString(dlg, kRemoteLogin, name);
  103.     SetItemMaxLength(dlg, kRemoteLogin, 255);
  104.     strcpy(password, gPrefs.remotePassword);
  105.     len = strlen(password);
  106.     memset(tempStr, '•', len);
  107.     tempStr[len] = 0;
  108.     DlgSetCString(dlg, kRemotePassword, tempStr);
  109.     SetItemPassword(dlg, kRemotePassword, password);
  110.     SetItemMaxLength(dlg, kRemotePassword, 31);
  111.     strcpy(path, gPrefs.remotePath);
  112.     DlgSetCString(dlg, kRemotePath, path);
  113.     SetItemMaxLength(dlg, kRemotePath, 31);
  114.     DlgSetCheck(dlg, kAutoGetPut, gPrefs.autoFetchnewsrc);
  115.     DlgSetCheck(dlg, kSavePassword, gPrefs.savePassword);
  116.     if (*host == 0) {
  117.         SelIText(dlg, kRemoteHost, 0, 0);
  118.     } else if (*name == 0) {
  119.         SelIText(dlg, kRemoteLogin, 0, 0);
  120.     } else if (*password == 0) {
  121.         SelIText(dlg, kRemotePassword, 0, 0);
  122.     } else if (*path == 0) {
  123.         SelIText(dlg, kRemotePath, 0, 0);
  124.     } else {
  125.         SelIText(dlg, kRemoteHost, 0, 0x7fff);
  126.     }
  127.     
  128.     do {
  129.         DlgEnableItem(dlg, ok, *host != 0 && *name != 0 && *password != 0 && *path != 0);
  130.         MyModalDialog(DialogFilter, &item, true, true);
  131.         switch (item) {
  132.             case kRemoteHost:
  133.                 DlgGetCString(dlg, item, host);
  134.                 break;
  135.             case kRemoteLogin:
  136.                 DlgGetCString(dlg, item, name);
  137.                 break;
  138.             case kRemotePath:
  139.                 DlgGetCString(dlg, item, path);
  140.                 break;
  141.             case kAutoGetPut:
  142.             case kSavePassword:
  143.                 DlgToggleCheck(dlg, item);
  144.                 break;
  145.         }
  146.     } while (item != ok && item != cancel);
  147.  
  148.     if (item == ok) {
  149.         strcpy(gPrefs.host, host);
  150.         strcpy(gPrefs.name, name);
  151.         strcpy(gPrefs.remotePassword, password);
  152.         strcpy(gPrefs.remotePath, path);
  153.         gPrefs.autoFetchnewsrc = DlgGetCheck(dlg, kAutoGetPut);
  154.         gPrefs.savePassword = DlgGetCheck(dlg, kSavePassword);
  155.     }
  156.     MyDisposDialog(dlg);
  157.     return item == ok;
  158. }
  159.  
  160.  
  161.  
  162. /*----------------------------------------------------------------------------
  163.     Skip 
  164.     
  165.     Skips white space in a newsrc line.
  166. ----------------------------------------------------------------------------*/
  167.  
  168. static void Skip (void)
  169. {
  170.     while (*gPos == ' ' || *gPos == '\t') gPos++;
  171. }
  172.  
  173.  
  174.  
  175. /*----------------------------------------------------------------------------
  176.      GetNumber 
  177.      
  178.      Parses a number from a newsrc line. Leading white space is skipped.
  179.  
  180.     Exit:    function result = parsed number, or -1 if syntax error.
  181. ----------------------------------------------------------------------------*/
  182.  
  183. static long GetNumber (void)
  184. {
  185.     long num;
  186.     char *endp;
  187.     
  188.     errno = 0;
  189.     num = strtol(gPos, &endp, 10);
  190.     if (num <= 0 || errno != 0) return -1;
  191.     gPos = endp;
  192.     return num;
  193. }
  194.  
  195.  
  196.  
  197. /*----------------------------------------------------------------------------
  198.     GetUnreadList 
  199.     
  200.     Parses the list of read article ranges in a newsrc line and
  201.     converts it to a linked list of unread article ranges.
  202.  
  203.     Exit:    function result = true if list parsed, false if error.
  204.             gTheGroup.unread = handle to unread list.
  205.             gTheGroup.lastMess = highest article number read, or 0 if 
  206.                 unread list is empty.
  207. ----------------------------------------------------------------------------*/
  208.  
  209. static Boolean GetUnreadList (void)
  210. {
  211.     long firstUnread, firstRead, lastRead;
  212.     TUnread **prev, **cur;
  213.  
  214.     gTheGroup.unread = nil;
  215.     gTheGroup.numUnread = 0;
  216.     
  217.     firstUnread = 1;
  218.     lastRead = 0;
  219.     Skip();
  220.     while (*gPos && *gPos != CR) {
  221.         firstRead = GetNumber();
  222.         if (firstRead == 0 && firstUnread == 1) firstRead = 1;
  223.         if (firstRead < firstUnread) goto exit1;
  224.         Skip();
  225.         if (*gPos == '-') {
  226.             gPos++;
  227.             lastRead = GetNumber();
  228.             if (lastRead < firstRead) goto exit1;
  229.         } else {
  230.             lastRead = firstRead;
  231.         }
  232.         if (firstUnread < firstRead) AppendUnreadRange(firstUnread, firstRead-1, &gTheGroup);
  233.         firstUnread = lastRead+1;
  234.         Skip();
  235.         if (*gPos == ',') {
  236.             gPos++;
  237.             Skip();
  238.         }
  239.     }
  240.     AppendUnreadRange(firstUnread, 0x7fffffff, &gTheGroup);
  241.     gTheGroup.lastMess = lastRead;
  242.     
  243.     if (*gPos == CR) gPos++;
  244.     return true;
  245.  
  246. exit1:
  247.     cur = gTheGroup.unread;
  248.     while (cur != nil) {
  249.         prev = cur;
  250.         cur = (**cur).next;
  251.         MyDisposHandle((Handle)prev);
  252.     }
  253.     return false;
  254. }
  255.  
  256.  
  257.  
  258. /*----------------------------------------------------------------------------
  259.     RecordDeletedGroup 
  260.     
  261.     Records a user error message for a group which was deleted while
  262.     parsing a newsrc file.
  263.     
  264.     Entry:    groupName = the group name.
  265.             reason = the reason the group was deleted:
  266.                 1: Syntax error in newsrc line.
  267.                 2: Group not in full group list.
  268.                 3: Group deleted on server.
  269. ----------------------------------------------------------------------------*/
  270.  
  271. static void RecordDeletedGroup (char *groupName, short reason)
  272. {
  273.     char *reasonStr;
  274.     char msg[512];
  275.     short len;
  276.  
  277.     switch (reason) {
  278.         case 1:
  279.             reasonStr = "Syntax error.";
  280.             break;
  281.         case 2:
  282.             reasonStr = "Group not in full group list.";
  283.             break;
  284.         case 3:
  285.             reasonStr = "Group deleted on news server.";
  286.             break;
  287.     }
  288.     sprintf(msg, "%s: %s\r", groupName, reasonStr);
  289.     len = strlen(msg);
  290.     if (gDeletedLen + len > gDeletedAllocated) {
  291.         gDeletedAllocated += 1000;
  292.         MySetHandleSize(gDeleted, gDeletedAllocated);
  293.     }
  294.     BlockMove(msg, *gDeleted + gDeletedLen, len);
  295.     gDeletedLen += len;
  296. }
  297.  
  298.  
  299.  
  300. /*----------------------------------------------------------------------------
  301.     ProcessOneNewsrcLine 
  302.     
  303.     Parses and processes one line from a newsrc list.
  304.  
  305.     Entry:    gPos = pointer to beginning of newsrc line.
  306.  
  307.     Exit:    gPos = pointer to beginning of next newsrc line.
  308.  
  309.     There are four possible kinds of newsrc lines:
  310.  
  311.     1. Lines with syntax errors. These lines are skipped.
  312.  
  313.     2. Lines for deleted groups (group name not in full group list). 
  314.        These lines are skipped.
  315.  
  316.     3. Subscribed lines. These lines are parsed and appended to the end of the
  317.        gUserGroupArray, with the following TGroup fields initialized:
  318.  
  319.        nameOffset = offset in gGroupNames of group name.
  320.        firstMess = 1.
  321.        lastMess = highest article number read on newsrc line, or 0 if read article
  322.           list is empty.
  323.        unread = unread list built assuming the range of articles in the group
  324.          is [1,maxlong]. This will be adjusted later when we learn the real
  325.          range of articles in the group.
  326.        status = 'x'. This indicates that we need to learn the range of articles
  327.          for the group.
  328.        onlyRedrawCount = false.
  329.  
  330.        The firstMess, lastMess, and numUnread fields are reset later when
  331.        we learn the real range of articles in the group.
  332.  
  333.     4. Unsubscribed lines. These lines are copied as is to the end of the
  334.        gUnsubscribed memory block. NewsWatcher doesn't do anything with these
  335.        lines except append them to the end of the newsrc file when it is later
  336.        sent to a remote host. This is for compatibility with UNIX newsreaders.
  337. ----------------------------------------------------------------------------*/
  338.  
  339. static void ProcessOneNewsrcLine(void)
  340. {
  341.     CStr255 groupName;
  342.     char *groupNameStart, *lineStart;
  343.     short index;
  344.     long len;
  345.  
  346.     lineStart = gPos;
  347.     Skip();
  348.     groupNameStart = gPos;
  349.     while (*gPos && *gPos != ':' && *gPos != '!' && *gPos != CR) gPos++;
  350.     len = gPos - groupNameStart;
  351.     if (len > 255) {
  352.         BlockMove(groupNameStart, groupName, 255);
  353.         groupName[255] = 0;
  354.         RecordDeletedGroup(groupName, 1);
  355.         if (gPrefs.logActionsToFile) LogNewsrcGroupNameTooLong(groupName);
  356.         goto exit;
  357.     }
  358.     BlockMove(groupNameStart, groupName, len);
  359.     groupName[len] = 0;
  360.     if (len == 0) {
  361.         RecordDeletedGroup("Missing group name", 1);
  362.         if (gPrefs.logActionsToFile) LogNewsrcNoGroupName();
  363.         goto exit;
  364.     }
  365.     
  366.     if (*gPos == ':') {
  367.     
  368.         /* subscribed line - parse it and append the new group to the end
  369.            of gUserGroupArray. */
  370.            
  371.         gPos++;
  372.         index = FindGroupIndex(groupName);
  373.         if (index == -1) {
  374.             RecordDeletedGroup(groupName, 2);
  375.             if (gPrefs.logActionsToFile) LogNewsrcGroupNotInFullGroupList(groupName);
  376.             goto exit;
  377.         }
  378.         gTheGroup.nameOffset = (*gGroupArray)[index].nameOffset;
  379.         gTheGroup.firstMess = 1;
  380.         if (!GetUnreadList()) {
  381.             RecordDeletedGroup(groupName, 1);
  382.             if (gPrefs.logActionsToFile) LogNewsrcReadListSyntaxError(groupName);
  383.             goto exit;
  384.         }
  385.         gTheGroup.status = 'x';
  386.         gTheGroup.onlyRedrawCount = false;
  387.         if (gNumUserGroups >= gNumUserGroupsAllocated) {
  388.             gNumUserGroupsAllocated += 50;
  389.             MySetHandleSize((Handle)gUserGroupArray, sizeof(TGroup)*gNumUserGroupsAllocated);
  390.         }
  391.         (*gUserGroupArray)[gNumUserGroups] = gTheGroup;
  392.         gNumUserGroups++;
  393.         if (gPrefs.logActionsToFile) LogNewsrcSubscribedOK(groupName);
  394.         return;
  395.                 
  396.     } else if (*gPos == '!') {
  397.     
  398.         /* unsubscribed line - append the line as is to the end of the
  399.            gUnsubscribed memory block. */
  400.            
  401.         while (*gPos && *gPos != CR) gPos++;
  402.         if (*gPos == CR) *gPos++;
  403.         len = gPos - lineStart;
  404.         if (gUnsubscribedLen + len > gUnsubscribedAllocated) {
  405.             gUnsubscribedAllocated += 10000;
  406.             MySetHandleSize(gUnsubscribed, gUnsubscribedAllocated);
  407.         }
  408.         BlockMove(lineStart, *gUnsubscribed + gUnsubscribedLen, len);
  409.         gUnsubscribedLen += len;
  410.         if (gPrefs.logActionsToFile) LogNewsrcUnsubscribedOK(groupName);
  411.         return;
  412.         
  413.     } else {
  414.     
  415.         RecordDeletedGroup(groupName, 1);
  416.         if (gPrefs.logActionsToFile) LogNewsrcNoColonOrBang(groupName);
  417.     
  418.     }
  419.  
  420. exit:
  421.  
  422.     /* syntax error - skip this line. */
  423.  
  424.     while (*gPos && *gPos != CR) gPos++;
  425.     if (*gPos == CR) gPos++;
  426. }
  427.  
  428.  
  429.  
  430. /*----------------------------------------------------------------------------
  431.     GetArticleRangeInfo 
  432.     
  433.     Queries the NNTP server to get current article range info for each group 
  434.     in the new user group array.
  435.  
  436.     Exit:    function result = true if info retrieved, false if error.
  437. ----------------------------------------------------------------------------*/
  438.  
  439. static Boolean GetArticleRangeInfo (void)
  440. {
  441.     short i = 0;
  442.     short numDeleted = 0;
  443.     TUnread **prev, **cur;
  444.     char *groupName;
  445.     
  446.     StatusWindow("Checking for new articles.");
  447.  
  448.     if (!GetGroupArrayArticleRanges(gUserGroupArray, gNumUserGroups))
  449.         return false;
  450.  
  451.     while (i < gNumUserGroups) {
  452.         gTheGroup = (*gUserGroupArray)[i];
  453.         if (gTheGroup.status == 'x') {
  454.             AdjustUnreadList(&gTheGroup);
  455.             gTheGroup.status = ' ';
  456.             (*gUserGroupArray)[i] = gTheGroup;
  457.             i++;
  458.         } else if (gTheGroup.status == 'd') {
  459.             cur = gTheGroup.unread;
  460.             while (cur != nil) {
  461.                 prev = cur;
  462.                 cur = (**cur).next;
  463.                 MyDisposHandle((Handle)prev);
  464.             }
  465.             gNumUserGroups--;
  466.             if (i < gNumUserGroups) {
  467.                 BlockMove(*gUserGroupArray+i+1, *gUserGroupArray+i,
  468.                     sizeof(TGroup)*(gNumUserGroups-i));
  469.             }
  470.             numDeleted++;
  471.             HLock(gGroupNames);
  472.             groupName = *gGroupNames + gTheGroup.nameOffset;
  473.             RecordDeletedGroup(groupName, 3);
  474.             if (gPrefs.logActionsToFile) 
  475.                 LogNewsrcGroupNotOnServer(groupName);
  476.             HUnlock(gGroupNames);
  477.         }
  478.     }
  479.     
  480.     if (numDeleted > 0)
  481.         MySetHandleSize((Handle)gUserGroupArray, sizeof(TGroup)*gNumUserGroups);
  482.     
  483.     return true;
  484. }
  485.  
  486.  
  487.  
  488. /*----------------------------------------------------------------------------
  489.     MakeUserGroupArrayFromNewsrc 
  490.     
  491.     Creates a new user group array from a newsrc-format list.
  492.  
  493.     Entry:    newsrc = handle to newsrc-format list, 0-terminated.
  494.  
  495.     Exit:    function result = true if no error.
  496.             *groupArray = handle to new user group array.
  497.             *numGroups = number of groups in array.
  498.             *unsubscribed = handle to list of unsubscribed groups in newsrc list.
  499.             *deleted = handle to list of deleted groups.
  500. ----------------------------------------------------------------------------*/
  501.  
  502. static Boolean MakeUserGroupArrayFromNewsrc(Handle newsrc, TGroup ***groupArray, 
  503.     short *numGroups, Handle *unsubscribed, Handle *deleted)
  504. {    
  505.     gUserGroupArray = (TGroup**)NewHandle(50*sizeof(TGroup));
  506.     gNumUserGroups = 0;
  507.     gNumUserGroupsAllocated = 50;
  508.     gUnsubscribed = NewHandle(10000);
  509.     gUnsubscribedLen = 0;
  510.     gUnsubscribedAllocated = 10000;
  511.     gDeleted = NewHandle(0);
  512.     gDeletedLen = 0;
  513.     gDeletedAllocated = 0;
  514.     
  515.     if (gPrefs.logActionsToFile) LogNewsrcParseBegin(newsrc);
  516.     MoveHHi(newsrc);
  517.     HLock(newsrc);
  518.     gPos = *newsrc;
  519.     while (*gPos) ProcessOneNewsrcLine();
  520.     HUnlock(newsrc);
  521.     
  522.     MySetHandleSize((Handle)gUserGroupArray, gNumUserGroups*sizeof(TGroup));
  523.     MySetHandleSize(gUnsubscribed, gUnsubscribedLen);
  524.  
  525.     if (!GetArticleRangeInfo()) goto exit1;
  526.     if (gPrefs.logActionsToFile) LogNewsrcParseEnd(gUserGroupArray, gNumUserGroups, gUnsubscribed);
  527.     *groupArray = gUserGroupArray;
  528.     *numGroups = gNumUserGroups;
  529.     *unsubscribed = gUnsubscribed;
  530.     MySetHandleSize(gDeleted, gDeletedLen);
  531.     *deleted = gDeleted;
  532.     return true;
  533.     
  534. exit1:
  535.     HUnlock(newsrc);
  536.     MyDisposHandle((Handle)gUserGroupArray);
  537.     MyDisposHandle(gUnsubscribed);
  538.     MyDisposHandle(gDeleted);
  539.     *groupArray = nil;
  540.     *numGroups = 0;
  541.     *unsubscribed = nil;
  542.     *deleted = nil;
  543.     return false;
  544. }
  545.  
  546.  
  547. /*----------------------------------------------------------------------------
  548.     MakeUserGroupWindowFromNewsrc 
  549.     
  550.     Creates a new user group list window from a newsrc-format list.
  551.  
  552.     Entry:    newsrc = handle to newsrc-format list, 0-terminated.
  553.             title = window title.
  554.             autoFetched = true if autofetched from host.
  555.             theFile = pointer to disk file FSSpec, or nil if fetched from host.
  556.             saved = true if from disk file, false if from host.
  557. ----------------------------------------------------------------------------*/
  558.  
  559. static void MakeUserGroupWindowFromNewsrc (Handle newsrc, StringPtr title,
  560.     Boolean autoFetched, FSSpec *theFile, Boolean saved)
  561. {
  562.     TGroup **groupArray;
  563.     Handle unsubscribed;
  564.     Handle deleted;
  565.     WindowPtr wind;
  566.     TWindow **info;
  567.     short numGroups;
  568.     DialogPtr dlg;
  569.     short fontNum;
  570.     short item;
  571.  
  572.     if (!MakeUserGroupArrayFromNewsrc(newsrc, &groupArray, 
  573.         &numGroups, &unsubscribed, &deleted)) return;
  574.     
  575.     wind = NewUserGroupWindow(title, groupArray, numGroups);
  576.     info = (TWindow**)GetWRefCon(wind);        
  577.     (**info).unsubscribed = unsubscribed;
  578.     (**info).autoFetched = autoFetched;
  579.     if (theFile) (**info).theFile = *theFile;
  580.     (**info).saved = saved;
  581.     ShowWindow(wind);
  582.  
  583.     if (GetHandleSize(deleted) != 0) {
  584.         (**info).changed = true;
  585.         dlg = MyGetNewDialog(kDeletedGroupsDlg);
  586.         GetFNum("\pMonaco", &fontNum);
  587.         MySetHandleSize(deleted, GetHandleSize(deleted) - 1);
  588.         SetItemReadOnly(dlg, kDeletedGroupsTheInfoItem, deleted, fontNum, 9);
  589.         MyModalDialog(DialogFilter, &item, false, true);
  590.         MyDisposDialog(dlg);
  591.     }
  592.     MyDisposHandle(deleted);
  593. }
  594.  
  595.  
  596.  
  597. /*----------------------------------------------------------------------------
  598.     OpenFile 
  599.     
  600.     Opens a user group list from a disk file.
  601.  
  602.     Entry:    theFile = the file to be opened.
  603. ----------------------------------------------------------------------------*/
  604.  
  605. void OpenFile (FSSpec *theFile)
  606. {
  607.     OSErr err;
  608.     short fRefNum = 0;
  609.     long length;
  610.     Handle newsrc = nil;
  611.     CStr255 msg;
  612.     FInfo fndrInfo;
  613.     WindowPtr wind;
  614.     TWindow **info;
  615.     FSSpec theWindowFile;
  616.     
  617.     /* Check to make sure the file has the proper creator and type. */
  618.  
  619.     err = FSpGetFInfo(theFile, &fndrInfo);
  620.     if (err != noErr) goto exit1;
  621.     if (fndrInfo.fdType != kFType  || 
  622.         fndrInfo.fdCreator != kFCreator ) return;
  623.  
  624.     /* Check to see if the file is already open. If it is, bring its
  625.        window to the front. */
  626.     
  627.     for (wind = FrontWindow(); wind != nil; wind = (WindowPtr)((WindowPeek)wind)->nextWindow) {
  628.         if (IsAppWindow(wind)) {
  629.             info = (TWindow**)GetWRefCon(wind);
  630.             if ((**info).kind == kUserGroup) {
  631.                 theWindowFile = (**info).theFile;
  632.                 if (IsEqualFSSpec(&theWindowFile, theFile)) {
  633.                     SelectWindow(wind);
  634.                     return; 
  635.                 }
  636.             }
  637.         }
  638.     }
  639.                
  640.     /* Open the file in a new window. */    
  641.     
  642.     err = FSpOpenDF(theFile, fsRdPerm, &fRefNum);
  643.     if (err != noErr) goto exit1;
  644.     err = GetEOF(fRefNum, &length);
  645.     if (err != noErr) goto exit1;
  646.     newsrc = MyNewHandle(length+1);
  647.     HLock(newsrc);
  648.     err = FSRead(fRefNum, &length, *newsrc);
  649.     HUnlock(newsrc);
  650.     if (err != noErr) goto exit1;
  651.     (*newsrc)[length] = 0;
  652.     
  653.     MakeUserGroupWindowFromNewsrc(newsrc, theFile->name, false, theFile, true);
  654.     
  655. exit:
  656.     MyDisposHandle(newsrc);
  657.     if (fRefNum != 0) FSClose(fRefNum);
  658.     return;
  659.     
  660. exit1:
  661.     p2cstr(theFile->name);
  662.     sprintf(msg, "Unexpected error %d while trying to open file “%s”", 
  663.         err, theFile->name);
  664.     c2pstr((char*)theFile->name);
  665.     ErrorMessage(msg);
  666.     goto exit;
  667. }
  668.  
  669.  
  670.  
  671. /*----------------------------------------------------------------------------
  672.     DoOpenGroupList 
  673.     
  674.     Handles the "Open Group List" command.
  675. ----------------------------------------------------------------------------*/
  676.  
  677. void DoOpenGroupList (void)
  678. {
  679.     StandardFileReply reply;
  680.     
  681.     MyStandardGetFile(nil, 1, (OSType*)"NEWS", &reply);
  682.     if (reply.sfGood) OpenFile(&reply.sfFile);
  683. }
  684.  
  685.  
  686.  
  687. /*----------------------------------------------------------------------------
  688.     DoGetGroupListFromHost 
  689.     
  690.     Handles the "Get Group List from Host" command.
  691.  
  692.     Entry:    autoFetch = true if startup call to autoFetch group list from host.
  693. ----------------------------------------------------------------------------*/
  694.  
  695. void DoGetGroupListFromHost (Boolean autoFetch)
  696. {
  697.     Handle newsrc = nil;
  698.  
  699.     /*If this is a startup autofetch and the user has "Save Password" turned on,
  700.      * don't put up the confirmation dialog unless needed info is missing. */
  701.      
  702.     if (!(autoFetch && gPrefs.savePassword && *gPrefs.host &&
  703.             *gPrefs.name && *gPrefs.remotePassword && *gPrefs.remotePath)) {
  704.         if (!DoHostDialog()) return;
  705.     }
  706.         
  707.     newsrc = MyNewHandle(0);
  708.     
  709.     StatusWindow("Getting group list from host.");
  710.     if (!FTPGetFile(gPrefs.host, gPrefs.name, gPrefs.remotePassword, 
  711.         gPrefs.remotePath, newsrc))
  712.         goto exit;
  713.  
  714.     MakeUserGroupWindowFromNewsrc(newsrc, "\p.newsrc", autoFetch, nil, false);
  715.  
  716.     if (autoFetch) {
  717.         strcpy(gAutoFetchHost,gPrefs.host);
  718.         strcpy(gAutoFetchName,gPrefs.name);
  719.         strcpy(gAutoFetchPass,gPrefs.remotePassword);
  720.         strcpy(gAutoFetchPath,gPrefs.remotePath);
  721.     }
  722.  
  723. exit:
  724.     MyDisposHandle(newsrc);
  725. }
  726.  
  727.  
  728.  
  729. /*----------------------------------------------------------------------------
  730.     AppendStrToNewsrc 
  731.     
  732.     Appends a string to the newsrc.
  733.  
  734.     Entry:    str = pointer to string to append to newsrc.
  735. ----------------------------------------------------------------------------*/
  736.  
  737.  static void AppendStrToNewsrc (char *str)
  738.  {
  739.      long len;
  740.      
  741.      len = strlen(str);
  742.      if (gNewsrcLength + len > gNewsrcAllocated) {
  743.          gNewsrcAllocated += 10000;
  744.          MySetHandleSize(gNewsrc, gNewsrcAllocated);
  745.      }
  746.      BlockMove(str, *gNewsrc + gNewsrcLength, len);
  747.      gNewsrcLength += len;
  748.  }
  749.  
  750.  
  751.  
  752. /*----------------------------------------------------------------------------
  753.     MakeNewsrcFromUserGroupListWindow 
  754.     
  755.     Makes a newsrc list from a user group list window.
  756.  
  757.     Entry:    wind = pointer to user group list window.
  758.  
  759.     Exit:    function result = true if no error, false if error.
  760.             *newsrc = handle to newsrc list.
  761.             *length = length of newsrc list.
  762. ----------------------------------------------------------------------------*/
  763.  
  764.  static Boolean MakeNewsrcFromUserGroupListWindow (WindowPtr wind, 
  765.      Handle *newsrc, long *length)
  766.  {
  767.      TWindow **info;
  768.      ListHandle theList;
  769.      TGroup **groupArray;
  770.      Handle strings;
  771.      Cell theCell;
  772.      short numCells, cellData, cellDataLen;
  773.      TGroup theGroup;
  774.      long first, last;
  775.      TUnread **unread;
  776.      CStr255 tmpStr;
  777.      TChild **childListEl;
  778.      Handle unsubscribed;
  779.      Boolean firstRange;
  780.      
  781.      gNewsrc = MyNewHandle(10000);
  782.      gNewsrcLength = 0;
  783.      gNewsrcAllocated = 10000;
  784.      
  785.      info = (TWindow**)GetWRefCon(wind);
  786.      theList = (**info).theList;
  787.      groupArray = (**info).groupArray;
  788.      strings = (**info).strings;
  789.      
  790.      if (gPrefs.logActionsToFile) LogNewsrcWriteBegin(groupArray, (**info).numGroups, 
  791.          (**info).unsubscribed);
  792.      
  793.      for (childListEl = (**info).childList; childListEl != nil; 
  794.          childListEl = (**childListEl).next) UpdateUnreadList((**childListEl).childWindow);
  795.      
  796.      theCell.h = 0;
  797.      numCells = (**theList).dataBounds.bottom;
  798.      HLock(strings);
  799.      for (theCell.v = 0; theCell.v < numCells; theCell.v++) {
  800.          cellDataLen = 2;
  801.          LGetCell(&cellData, &cellDataLen, theCell, theList);
  802.          theGroup = (*groupArray)[cellData];
  803.          AppendStrToNewsrc(*strings + theGroup.nameOffset);
  804.          AppendStrToNewsrc(": ");
  805.          first = 1;
  806.          firstRange = true;
  807.          for (unread = theGroup.unread; unread != nil; unread = (**unread).next) {
  808.              last = (**unread).firstUnread-1;
  809.              if (first <= last) {
  810.                  if (first < last) {
  811.                      sprintf(tmpStr, "%lu-%lu", first, last);
  812.                  } else {
  813.                      sprintf(tmpStr, "%lu", first);
  814.                  }
  815.                  if (!firstRange) AppendStrToNewsrc(",");
  816.                  AppendStrToNewsrc(tmpStr);
  817.                  firstRange = false;
  818.              }
  819.              first = (**unread).lastUnread + 1;
  820.          }
  821.          last = theGroup.lastMess;
  822.          if (first <= last) {
  823.              if (first < last) {
  824.                  sprintf(tmpStr, "%lu-%lu", first, last);
  825.              } else {
  826.                  sprintf(tmpStr, "%lu", first);
  827.              }
  828.              if (!firstRange) AppendStrToNewsrc(",");
  829.              AppendStrToNewsrc(tmpStr);
  830.          }
  831.          AppendStrToNewsrc(CRSTR);
  832.      }
  833.      HUnlock(strings);
  834.           
  835.      MySetHandleSize(gNewsrc, gNewsrcLength);
  836.      
  837.      if (gPrefs.logActionsToFile) LogNewsrcWriteEnd(gNewsrc);
  838.  
  839.      *newsrc = gNewsrc;
  840.      *length = gNewsrcLength;
  841.      return true;
  842.  }
  843.  
  844.  
  845.  
  846. /*----------------------------------------------------------------------------
  847.     SaveFile 
  848.     
  849.     Saves a user group list window to a disk file.
  850.  
  851.     Entry:    wind = pointer to user group list window.
  852.             theFile = the file.
  853.             scriptTag = script code.
  854.  
  855.     Exit:    function result = true if file saved, false if error.
  856. ----------------------------------------------------------------------------*/
  857.  
  858. static Boolean SaveFile (WindowPtr wind, FSSpec *theFile, ScriptCode scriptTag)
  859. {
  860.     Handle newsrc = nil;
  861.     long length;
  862.     short fRefNum = 0;
  863.     OSErr err;
  864.     Boolean result;
  865.     CStr255 msg;
  866.  
  867.     if (!MakeNewsrcFromUserGroupListWindow(wind, &newsrc, &length)) 
  868.         return false;
  869.     
  870.     err = FSpOpenDF(theFile, fsRdWrPerm, &fRefNum);
  871.     if (err == noErr) {
  872.         err = SetEOF(fRefNum, 0);
  873.         if (err != noErr) goto exit1;
  874.     } else if (err = fnfErr) {
  875.         err = FSpCreate(theFile, kFCreator, kFType, scriptTag);
  876.         if (err != noErr) goto exit1;
  877.         err = FSpOpenDF(theFile, fsRdWrPerm, &fRefNum);
  878.         if (err != noErr) goto exit1;
  879.     } else {
  880.         goto exit1;
  881.     }
  882.     
  883.     HLock(newsrc);
  884.     err = FSWrite(fRefNum, &length, *newsrc);
  885.     HUnlock(newsrc);
  886.     result = true;
  887.     
  888. exit:
  889.     MyDisposHandle(newsrc);
  890.     if (fRefNum != 0) FSClose(fRefNum);
  891.     return result;
  892.     
  893. exit1:
  894.     sprintf(msg, "Unexpected error %d while trying to save file “%#s”", 
  895.         err, theFile->name);
  896.     ErrorMessage(msg);
  897.     result = false;
  898.     goto exit;
  899. }
  900.  
  901.  
  902.  
  903. /*----------------------------------------------------------------------------
  904.     DoSaveAs 
  905.     
  906.     Handles the "Save As" command.
  907.  
  908.     Entry:    wind = pointer to user group list window.
  909.  
  910.     Exit:    function result = true if file saved, false if canceled or error.
  911. ----------------------------------------------------------------------------*/
  912.  
  913. Boolean DoSaveAs (WindowPtr wind)
  914. {
  915.     StandardFileReply reply;
  916.     TWindow **info;
  917.     Boolean good;
  918.     Str255 fName;
  919.     
  920.     info = (TWindow**)GetWRefCon(wind);
  921.     
  922.     GetWTitle(wind, fName);
  923.     MyStandardPutFile("\pSave group list as:", fName, &reply);
  924.     if (!reply.sfGood) return false;
  925.         
  926.     if (good = SaveFile(wind, &reply.sfFile, reply.sfScript)) {
  927.         RemoveWindMenu(wind);
  928.         SetWTitle(wind, reply.sfFile.name);
  929.         AddWindMenu(wind);
  930.         (**info).theFile = reply.sfFile;
  931.         (**info).changed = false;
  932.         (**info).saved = true;
  933.     }
  934.     return good;
  935. }
  936.  
  937.  
  938.  
  939. /*----------------------------------------------------------------------------
  940.     DoSave 
  941.     
  942.     Handles the "Save" command.
  943.  
  944.     Entry:    wind = pointer to user group list window.
  945.  
  946.     Exit:    function result = true if file saved, false if canceled or error.
  947. ----------------------------------------------------------------------------*/
  948.  
  949. Boolean DoSave (WindowPtr wind)
  950. {
  951.     TWindow **info;
  952.     FSSpec theFile;
  953.     Boolean result;
  954.     
  955.     info = (TWindow**)GetWRefCon(wind);
  956.     if ((**info).saved) {
  957.         theFile = (**info).theFile;
  958.         if (result = SaveFile(wind, &theFile, (**info).scriptTag))
  959.             (**info).changed = false;
  960.         return result;
  961.     } else {
  962.         return DoSaveAs(wind);
  963.     }
  964. }
  965.  
  966.  
  967.  
  968. /*----------------------------------------------------------------------------
  969.     CheckForSave 
  970.     
  971.     Asks the user if he wishes to save a user group list window, and saves it 
  972.     if the user says yes.
  973.  
  974.     Entry:    wind = pointer to user group list window.
  975.  
  976.     Exit:    function result = true if file saved or user clicked the 
  977.                 "Don't Save" button or okToCloseIfChanged flag is set.
  978.             function result = false if save operation failed or user
  979.                 clicked the "Cancel" button.
  980. ----------------------------------------------------------------------------*/
  981.  
  982.  
  983. Boolean CheckForSave (WindowPtr wind)
  984. {
  985.     TWindow **info;
  986.     DialogPtr dlg;
  987.     short item;
  988.     Str255 fName;
  989.     
  990.     info = (TWindow**)GetWRefCon(wind);
  991.     if ((**info).okToCloseIfChanged) return true;
  992.     GetWTitle(wind, fName);
  993.     ParamText(fName, "\p", "\p", "\p");
  994.     dlg = MyGetNewDialog(kCheckSaveID);
  995.     SetItemKeyEquivalent(dlg, 3, 'D');
  996.     MyModalDialog(DialogFilter, &item, true, true);
  997.     MyDisposDialog(dlg);
  998.     switch (item) {
  999.         case 1: /* save */
  1000.             return (DoSave(wind));
  1001.             break;
  1002.         case 2: /* cancel */
  1003.             return false;
  1004.             break;
  1005.         case 3: /* don't save */
  1006.             return true;
  1007.             break;
  1008.     }
  1009. }
  1010.  
  1011.  
  1012.  
  1013. /*----------------------------------------------------------------------------
  1014.     DoSendGroupListToHost handles the "Send Group List to Host" command.
  1015.  
  1016.     Entry:    wind = pointer to window.
  1017.             host = host name.
  1018.             name = username on host.
  1019.             pass = password.
  1020. */
  1021.  
  1022. void DoSendGroupListToHost (WindowPtr wind, char *host, char *name, char *pass, 
  1023.     char *path)
  1024. {
  1025.     TWindow **info;
  1026.     Handle newsrc = nil;
  1027.     long length;
  1028.     
  1029.     info = (TWindow**)GetWRefCon(wind);
  1030.  
  1031.     if (*host == 0) {
  1032.         if (!DoHostDialog()) return;
  1033.         host = gPrefs.host;
  1034.         name = gPrefs.name;
  1035.         pass = gPrefs.remotePassword;
  1036.         path = gPrefs.remotePath;
  1037.     }
  1038.  
  1039.     StatusWindow("Sending group list to host.");
  1040.     
  1041.     if (!MakeNewsrcFromUserGroupListWindow(wind, &newsrc, &length)) 
  1042.         return;
  1043.         
  1044.     HLock((**info).unsubscribed);
  1045.     HandAndHand((**info).unsubscribed, newsrc);
  1046.     HUnlock((**info).unsubscribed);
  1047.     HLock(newsrc);
  1048.     if (FTPPutFile(host, name, pass, path, *newsrc, GetHandleSize(newsrc))) 
  1049.         (**info).changed = false;
  1050.     HUnlock(newsrc);
  1051.         
  1052.     MyDisposHandle(newsrc);
  1053. }
  1054.